/**
 * Copyright 2014 opencxa.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.cxa.rpc.provider;

import org.cxa.misc.stringUtils.DelimitedStringBuilder;

import com.google.gson.annotations.Expose;


/**
 * @author Christopher Armenio
 *
 */
public class RpcException extends RuntimeException
{
	private static final long serialVersionUID = 7707521578977995686L;
	
	
	public enum RpcError
	{
		UNKNOWN_ERROR(0, "an unknown error occurred"),
		UNKNOWN_COMPONENT(1, "path in 'dest' field points to unknown component"),
		UNKNOWN_COMMAND(2, "component does not implement specified command"),
		INTERNAL_ERROR(3, "an error occured within the cxa.rpc framework"),
		ERROR_COMMAND_HANDLER(4, "an error ocurred in the command handler"),
		
		MISSING_PARAMETER(10, "the specified command requires a parameter which was missing"),
		EXTRA_PARAMETER(11, "an extra, unexpected parameter was provided"), 
		INVALID_PARAMETER_TYPE(12, "the provided parameter is of the wrong type"),
		INVALID_PARAMETER_VALUE(13, "the value of the provided parameter is invalid"),
		
		DEVICE_OFFLINE(20, "the specified device does not exist or is not online"),
		INVALID_DEST_PATH(21, "the destination path format is invalid"),
		REQUEST_TIMEOUT(22, "request timed out"),
		
		AUTH_INVALID_CREDS(30, "invalid authentication credentials"),
		INSUFFICIENT_PERMISSIONS(31, "insufficient permissions");
		
		
		private final Integer errorCode;
		private final String desc;
		private RpcError(Integer errorCodeIn, String userDescIn)
		{
			this.errorCode = errorCodeIn;
			this.desc = userDescIn;
		}
		
		public Integer getErrorCode() { return this.errorCode; }
		public String getDescription() { return this.desc; }
		
		public static RpcError rpcErrorFromErrorCode(Integer errorCodeIn)
		{
			for( RpcError currErr : RpcError.values() )
			{
				if( currErr.errorCode == errorCodeIn ) return currErr;
			}
			return RpcError.UNKNOWN_ERROR;
		}
	}
	
	
	private RpcError error = null;
	@Expose private Integer errorCode = null;
	@Expose private String desc = null;
	@Expose protected String detail = null;
	
	
	private RpcException(RpcError errorIn)
	{
		this.error = errorIn;
		this.errorCode = this.error.getErrorCode();
		this.desc = this.error.getDescription();
	}
	
	
	private RpcException(RpcError errorIn, String detailIn)
	{
		this.error = errorIn;
		this.errorCode = this.error.getErrorCode();
		this.desc = this.error.getDescription();
		this.detail = detailIn;
	}
	
	
	public RpcError getRpcError()
	{
		// if this was deserialized our RpcError might not exist...so create it
		if( this.error == null ) this.error = RpcError.rpcErrorFromErrorCode(this.errorCode);
		return this.error;
	}
	
	
	public String getDetails()
	{
		return this.detail;
	}
	
	
	public String getFormattedErrorString()
	{
		DelimitedStringBuilder dsb = new DelimitedStringBuilder("\n");
		dsb.append(String.format("Description: %s", this.getRpcError().getDescription()));
		
		if( this.detail != null ) dsb.append(String.format("Detail: %s", this.detail));
		return dsb.toString();
	}
	
	
	public static RpcException exceptionFromError(RpcError errorIn)
	{
		return new RpcException(errorIn);
	}
	
	
	public static RpcException exceptionFromError(RpcError errorIn, String detailIn)
	{
		return new RpcException(errorIn, detailIn);
	}
	
	
	public static RpcException extraParameterException(String paramNameIn, Class<?> parameterClassIn)
	{
		if( paramNameIn == null ) paramNameIn = "<unknown>";
		if( parameterClassIn == null ) parameterClassIn = Object.class;
		
		return new RpcException(RpcError.EXTRA_PARAMETER, String.format("name: '%s'  type: '%s'", paramNameIn, parameterClassIn.getSimpleName()));
	}
	
	
	public static RpcException invalidParameterTypeException(String paramNameIn, Class<?> parameterClassIn)
	{
		if( paramNameIn == null ) paramNameIn = "<unknown>";
		if( parameterClassIn == null ) parameterClassIn = Object.class;
		
		return new RpcException(RpcError.INVALID_PARAMETER_TYPE, String.format("name: '%s'  type: '%s'", paramNameIn, parameterClassIn.getSimpleName()));
	}
	
	
	public static RpcException invalidParameterValueException(String paramNameIn, Class<?> parameterClassIn)
	{
		if( paramNameIn == null ) paramNameIn = "<unknown>";
		if( parameterClassIn == null ) parameterClassIn = Object.class;
		
		return new RpcException(RpcError.INVALID_PARAMETER_VALUE, String.format("name: '%s'  type: '%s'", paramNameIn, parameterClassIn.getSimpleName()));
	}
	
	
	public static RpcException missingParameterTypeException(String paramNameIn, Class<?> parameterClassIn)
	{
		if( paramNameIn == null ) paramNameIn = "<unknown>";
		if( parameterClassIn == null ) parameterClassIn = Object.class;
		
		return new RpcException(RpcError.MISSING_PARAMETER, String.format("name: '%s'  type: '%s'", paramNameIn, parameterClassIn.getSimpleName()));
	}
	
	
	public static RpcException commandHandlerException(String detailIn)
	{
		return new RpcException(RpcError.ERROR_COMMAND_HANDLER, detailIn);
	}
}
